﻿// (c)2010 David Schwarz (david.schwarz@live.com)
// This file is licensed under GNU GPL v2 license, see more at http://www.gnu.org/licenses/gpl-2.0.html
namespace Z2.Core
{

	#region Using

	using System;
	using System.Collections.Generic;
	using Microsoft.Xna.Framework;
	using Microsoft.Xna.Framework.Content;
	using Microsoft.Xna.Framework.Graphics;

	#endregion

	/// <summary>
	/// Manažer sprajtů
	/// </summary>
	public class SpriteManager : IDisposable
	{

		#region Consts

		private const string GRAPHICS_DIRECTORY = "Graphics";
		private const string DEFAULT_GROUP = "common";

		#endregion

		#region Members

		private static object _syncRoot = new object();
		private static volatile SpriteManager _instance;

		private GraphicsDeviceManager m_graphics;
		private ContentManager m_contentManager;
		private List<Sprite> m_sprites;
		private Dictionary<string, Texture2D> m_textures;
		private Dictionary<string, List<string>> m_groups;
		private bool m_disposed;
		private bool m_initialized;

		#endregion

		#region Constructor

		/// <summary>
		/// Konstruktor
		/// </summary>
		protected SpriteManager()
		{
			this.m_sprites = new List<Sprite>();
			this.m_textures = new Dictionary<string, Texture2D>();
			this.m_groups = new Dictionary<string, List<string>>();
			this.m_disposed = false;
		}

		#endregion

		#region Properties

		/// <summary>
		/// Vrací instanci manažeru
		/// </summary>
		public static SpriteManager Instance
		{
			get
			{
				if (_instance == null)
				{
					lock (_syncRoot)
					{
						if (_instance == null)
							_instance = new SpriteManager();
					}
				}
				return _instance;
			}
		}

		/// <summary>
		/// Vrací <see cref="GraphicsDeviceManager"/>
		/// </summary>
		public GraphicsDeviceManager Graphics
		{
			get
			{
				return this.m_graphics;
			}
		}

		/// <summary>
		/// Vrací <see cref="ContentManager"/>
		/// </summary>
		public ContentManager Content
		{
			get
			{
				return this.m_contentManager;
			}
		}

		#endregion

		#region Methods

		/// <summary>
		/// Metoda zinicializuje manažer.
		/// </summary>
		/// <param name="contentManager">Platná instance <see cref="ContentManager"/></param>
		/// <param name="graphicsManager">Platná instance <see cref="GraphicsDeviceManager"/></param>
		public void Initialize(
			ContentManager contentManager,
			GraphicsDeviceManager graphicsManager)
		{
			if (this.m_initialized)
			{
				throw new InvalidOperationException();
			}

			this.m_contentManager = contentManager;
			this.m_graphics = graphicsManager;

			this.m_initialized = true;
		}

		/// <summary>
		/// Uvolní paměť dané skupiny textur a spritů
		/// </summary>
		/// <param name="group">Název skupiny</param>
		public void ReleaseGroup(string group)
		{
			if (this.m_groups.ContainsKey(group))
			{
				foreach (string alias in this.m_groups[group])
				{
					List<Sprite> sprites = this.m_sprites.FindAll(delegate(Sprite match) // Získáme seznam všech spritů
						{
							return match.Alias == alias;
						});
					sprites.ForEach(delegate(Sprite sprite) { sprite.Dispose(); }); // Všechny nalezené sprity disposnem
					// Na závěr je odstraníme z kolekce
					this.m_sprites.RemoveAll(delegate(Sprite match) { return match.Alias == alias; });

					// Pokud pod tímto aliasem existuje i textura
					List<string> remove = new List<string>();
					if(this.m_textures.ContainsKey(alias))
					{
						remove.Add(alias);
						this.m_textures[alias].Dispose();
					}

					remove.ForEach(delegate(string key) { this.m_textures.Remove(key); });
				}
			}
		}

		/// <summary>
		/// Metoda se pokusí načíst sprite z cache.
		/// </summary>
		/// <param name="texture">Název textury</param>
		/// <param name="horizontalFrames">Počet snímků horizontálně</param>
		/// <param name="verticalFrames">Počet snímků vertikálně</param>
		/// <param name="group">Název skupiny</param>
		/// <returns><see cref="Sprite"/></returns>
		public Sprite LoadSpriteCache(string texture, int horizontalFrames, int verticalFrames, string group)
		{
			// Vyhledáme sprite
			Sprite sprite = this.m_sprites.Find(delegate(Sprite match)
			{
				return match.Alias == texture;
			});

			// Nalezen, vracíme
			if (sprite != null)
			{
				return sprite;
			}

			// Vracíme nový
			return this.LoadSprite(texture, horizontalFrames, verticalFrames, group);
		}

		/// <summary>
		/// Metoda načte sprite
		/// </summary>
		/// <param name="texture">Název textury</param>
		/// <param name="horizontalFrames">Počet snímků horizontálně</param>
		/// <param name="verticalFrames">Počet snímků vertikálně</param>
		/// <returns><see cref="Sprite"/></returns>
		public Sprite LoadSprite(string texture, int horizontalFrames, int verticalFrames)
		{
			return this.LoadSprite(texture, horizontalFrames, verticalFrames, SpriteManager.DEFAULT_GROUP);
		}

		/// <summary>
		/// Metoda načte sprite
		/// </summary>
		/// <param name="texture">Název textury</param>
		/// <param name="horizontalFrames">Počet snímků horizontálně</param>
		/// <param name="verticalFrames">Počet snímků vertikálně</param>
		/// <param name="group">Název skupiny</param>
		/// <returns><see cref="Sprite"/></returns>
		public Sprite LoadSprite(string texture, int horizontalFrames, int verticalFrames, string group)
		{
			if(!this.m_initialized)
			{
				throw new Exception(); // TODO
			}

			// Pokud ještě neexistuje daná skupina
			if (!this.m_groups.ContainsKey(group)) // Založíme ji
				this.m_groups.Add(group, new List<string>());

			List<string> groupAliases = this.m_groups[group]; // Získáme obsah dané skupiny
			if (!groupAliases.Contains(texture)) // Přidáme aktuálně načtený alias, pokud tam ještě neexistuje
			{
				groupAliases.Add(texture);
			}

			Texture2D texture2D = null;
			if(this.m_textures.ContainsKey(texture)) // Kontrola existence textury
			{
				texture2D = this.m_textures[texture]; // Načteme z cache
			}
			else
			{
				// Jinak ji vytvoříme znova
				texture2D = this.m_contentManager.Load<Texture2D>(
					string.Format("{0}/{1}", SpriteManager.GRAPHICS_DIRECTORY, texture));
				this.m_textures.Add(texture, texture2D);
			}

			Sprite sprite = new Sprite(texture, texture2D, horizontalFrames, verticalFrames);

			this.m_sprites.Add(sprite);

			return sprite;
		}

		#endregion

		#region IDisposable Members

		/// <summary>
		/// Uvolnění paměti
		/// </summary>
		public void Dispose()
		{
			if (this.m_disposed)
			{
				return;
			}

			this.m_sprites.ForEach(delegate(Sprite sprite)
			{
				sprite.Dispose();
			});

			this.m_sprites.Clear();

			foreach (string key in this.m_textures.Keys)
			{
				this.m_textures[key].Dispose();
			}

			this.m_textures.Clear();

			foreach (string key in this.m_groups.Keys)
			{
				this.m_groups[key].Clear();
			}
			this.m_groups.Clear();

			this.m_disposed = true;
			GC.SuppressFinalize(this);
			GC.WaitForPendingFinalizers();
		}

		#endregion

	}
}
